import requests
import hashlib
import time
import json
import os
import logging
import socket
import sys
import signal
import psutil
from logging.handlers import TimedRotatingFileHandler
from typing import List, Dict, Optional, Set
from flask import Flask, request, jsonify
from flask_cors import CORS

# 用于Windows服务的库
try:
    import win32serviceutil
    import win32service
    import win32event
    import servicemanager
    import pythoncom

    IS_WINDOWS = True
except ImportError:
    IS_WINDOWS = False

# 企业微信事件加解密相关库
try:
    from Crypto.Cipher import AES
    from Crypto.Util.Padding import pad, unpad
    import base64
    import random
    import string

    CRYPTO_AVAILABLE = True
except ImportError:
    CRYPTO_AVAILABLE = False
    print("⚠️ 未安装加密库，事件订阅加密功能将不可用。执行 `pip install pycryptodome` 可启用加密功能")

# -------------------------- 1. 自动生成核心配置文件 --------------------------
CONFIG_FILE = os.path.join(os.path.dirname(__file__), "wecom_config.json")
# 默认配置模板（包含服务配置和事件订阅配置）
DEFAULT_CONFIG = {
    "corp_id": "wwad8a92820f6964ff",
    "app_secret": "KDS7EPbnT_CZj4eZLMv3jwfLqSVXSgvFvi7_iGqXaVI",
    "agent_id": 1000006,  # 企业微信agent_id是整数
    "redis_config": {
        "enable": False,
        "host": "localhost",
        "port": 6379,
        "db": 0,
        "password": ""
    },
    "cache_expire": {
        "token_expire": 7000,
        "ticket_expire": 7000
    },
    "service_config": {
        "service_name": "WeComApiService",
        "display_name": "企业微信API服务",
        "description": "提供企业微信相关功能的API服务",
        "max_restarts": 5,  # 最大重启次数
        "restart_delay": 60  # 重启延迟（秒）
    },
    "thread_config": {
        "enable_multithreading": True,  # 是否启用多线程
        "thread_count": 4  # 线程数量，默认4线程
    },
    "cors_config": {
        "allowed_origins": "*",  # 允许所有来源，生产环境建议指定具体域名
        "supports_credentials": True
    },
    "api_config": {
        "user_list_page_size": 100  # 用户列表分页大小
    },
    # 新增：事件订阅相关配置
    "event_config": {
        "token": "your_event_token",  # 事件回调验证token
        "encoding_aes_key": "your_encoding_aes_key",  # 消息加密密钥
        "callback_url": "/api/event-callback",  # 事件回调路径
        "enable_encryption": True,  # 是否启用消息加密
        "log_events": True  # 是否记录事件日志
    }
}


def init_config():
    """初始化配置：包含服务配置项和事件订阅配置"""
    if not os.path.exists(CONFIG_FILE):
        with open(CONFIG_FILE, "w", encoding="utf-8") as f:
            json.dump(DEFAULT_CONFIG, f, ensure_ascii=False, indent=4)
        print(f"✅ 配置文件不存在，已自动生成：{CONFIG_FILE}")
        print("ℹ️  请先编辑配置文件，替换corp_id、app_secret、agent_id为实际企业微信参数！")
        exit(1)

    try:
        with open(CONFIG_FILE, "r", encoding="utf-8") as f:
            config = json.load(f)

        # 确保必要配置存在
        if "service_config" not in config:
            config["service_config"] = DEFAULT_CONFIG["service_config"]
        if "thread_config" not in config:
            config["thread_config"] = DEFAULT_CONFIG["thread_config"]
        if "cors_config" not in config:
            config["cors_config"] = DEFAULT_CONFIG["cors_config"]
        if "api_config" not in config:
            config["api_config"] = DEFAULT_CONFIG["api_config"]
        # 新增：确保事件配置存在
        if "event_config" not in config:
            config["event_config"] = DEFAULT_CONFIG["event_config"]

        # 保存更新后的配置
        with open(CONFIG_FILE, "w", encoding="utf-8") as f:
            json.dump(config, f, ensure_ascii=False, indent=4)

        # 检查corp_id和app_secret（必须是字符串）
        if (isinstance(config.get("corp_id"), str) and config["corp_id"].startswith("替换") or
                isinstance(config.get("app_secret"), str) and config["app_secret"].startswith("替换")):
            print(f"❌ 配置文件未完善！请打开 {CONFIG_FILE}，替换corp_id、app_secret的占位符")
            exit(1)

        # 检查agent_id（必须是整数且不是默认占位符）
        if not isinstance(config.get("agent_id"), int) or config["agent_id"] == 1000000:
            print(f"❌ 配置文件未完善！请打开 {CONFIG_FILE}，将agent_id设置为实际的整数类型应用ID")
            exit(1)

        return config
    except json.JSONDecodeError as e:
        print(f"❌ 配置文件格式错误（JSON语法错误）：{str(e)}")
        exit(1)


# 加载配置
WECOM_CONFIG = init_config()
CORP_ID = WECOM_CONFIG["corp_id"]
APP_SECRET = WECOM_CONFIG["app_secret"]
AGENT_ID = WECOM_CONFIG["agent_id"]
REDIS_ENABLE = WECOM_CONFIG["redis_config"]["enable"]
REDIS_CONFIG = WECOM_CONFIG["redis_config"]
TOKEN_EXPIRE = WECOM_CONFIG["cache_expire"]["token_expire"]
TICKET_EXPIRE = WECOM_CONFIG["cache_expire"]["ticket_expire"]
SERVICE_CONFIG = WECOM_CONFIG["service_config"]
THREAD_CONFIG = WECOM_CONFIG["thread_config"]
ENABLE_MULTITHREADING = THREAD_CONFIG["enable_multithreading"]
THREAD_COUNT = THREAD_CONFIG["thread_count"]
CORS_CONFIG = WECOM_CONFIG["cors_config"]
API_CONFIG = WECOM_CONFIG["api_config"]
USER_LIST_PAGE_SIZE = API_CONFIG["user_list_page_size"]
# 新增：事件配置
EVENT_CONFIG = WECOM_CONFIG["event_config"]


# -------------------------- 2. 服务管理相关功能 --------------------------
def get_pid_by_service_name(service_name):
    """根据服务名称获取进程ID"""
    for proc in psutil.process_iter(['pid', 'name', 'cmdline']):
        try:
            cmdline = proc.cmdline()
            if any(service_name in arg for arg in cmdline):
                return proc.pid
        except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
            continue
    return None


def kill_existing_service(service_name):
    """终止已存在的同名服务"""
    pid = get_pid_by_service_name(service_name)
    if pid and pid != os.getpid():
        try:
            os.kill(pid, signal.SIGTERM)
            time.sleep(2)
            # 检查进程是否已终止，未终止则强制杀死
            if psutil.pid_exists(pid):
                os.kill(pid, signal.SIGKILL)
            logging.info(f"已终止同名服务进程 (PID: {pid})")
            return True
        except Exception as e:
            logging.error(f"终止现有服务失败: {str(e)}")
            return False
    return True


# -------------------------- 3. 初始化缓存（Redis/内存自动切换） --------------------------
redis_client = None
if REDIS_ENABLE:
    try:
        import redis

        # 基于配置初始化Redis
        redis_client = redis.Redis(
            host=REDIS_CONFIG["host"],
            port=REDIS_CONFIG["port"],
            db=REDIS_CONFIG["db"],
            password=REDIS_CONFIG["password"],
            decode_responses=True,  # 返回字符串格式，避免bytes处理
            socket_timeout=5  # 连接超时时间（秒）
        )
        # 测试Redis连接
        redis_client.ping()
        print("✅ Redis缓存初始化成功（生产环境推荐）")
    except ImportError:
        print("⚠️ Redis已启用，但未安装redis库！执行 `pip install redis` 可启用Redis，当前自动降级为内存缓存")
    except Exception as e:
        print(f"⚠️ Redis连接失败：{str(e)}，当前自动降级为内存缓存")
else:
    print("ℹ️ Redis未启用，使用内存缓存（仅单进程有效，适合开发环境）")


# -------------------------- 4. 企业微信消息加解密工具类 --------------------------
class WeComCrypto:
    """企业微信消息加解密工具类"""

    def __init__(self, token, encoding_aes_key, corp_id):
        self.token = token
        self.corp_id = corp_id

        # 解析encoding_aes_key
        self.encoding_aes_key = base64.b64decode(encoding_aes_key + '=')
        if len(self.encoding_aes_key) != 32:
            raise ValueError("无效的encoding_aes_key，必须是43位base64编码字符串")

        self.aes_key = self.encoding_aes_key
        self.mode = AES.MODE_CBC

    def get_random_str(self):
        """生成随机字符串"""
        return ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(16))

    def encrypt(self, text):
        """对明文进行加密"""
        # 拼接明文：随机字符串 + 4字节消息长度 + 明文 + 企业ID
        text = text.encode('utf-8')
        random_str = self.get_random_str().encode('utf-8')
        len_bytes = len(text).to_bytes(4, byteorder='big')
        corp_id_bytes = self.corp_id.encode('utf-8')
        content = random_str + len_bytes + text + corp_id_bytes

        # 加密
        cipher = AES.new(self.aes_key, self.mode, self.aes_key[:16])
        encrypted = cipher.encrypt(pad(content, AES.block_size))
        return base64.b64encode(encrypted).decode('utf-8')

    def decrypt(self, encrypted_text):
        """对密文进行解密"""
        # 解密
        cipher = AES.new(self.aes_key, self.mode, self.aes_key[:16])
        decrypted = unpad(cipher.decrypt(base64.b64decode(encrypted_text)), AES.block_size)

        # 提取明文
        # 16字节随机字符串 + 4字节长度 + 明文 + 企业ID
        if len(decrypted) < 20:
            raise ValueError("解密后的数据长度不足")

        length = int.from_bytes(decrypted[16:20], byteorder='big')
        text = decrypted[20:20 + length].decode('utf-8')
        corp_id = decrypted[20 + length:].decode('utf-8')

        if corp_id != self.corp_id:
            raise ValueError("解密后企业ID不匹配")

        return text


# -------------------------- 5. 企业微信核心工具类（封装所有业务逻辑） --------------------------
class WeComClient:
    def __init__(self):
        # 缓存Key（添加企业ID+应用ID前缀，避免多应用缓存冲突）
        self.TOKEN_CACHE_KEY = f"wecom:access_token:{CORP_ID}:{AGENT_ID}"
        self.TICKET_CACHE_KEY = f"wecom:jsapi_ticket:{CORP_ID}:{AGENT_ID}"
        # 内存缓存容器（Redis不可用时使用）
        self.memory_cache = {}
        # 初始化日志
        self.logger = logging.getLogger(__name__)

        # 新增：初始化事件加密工具
        self.event_config = EVENT_CONFIG
        self.crypto = None
        if self.event_config.get("enable_encryption", True) and CRYPTO_AVAILABLE:
            try:
                self.crypto = WeComCrypto(
                    self.event_config.get("token", ""),
                    self.event_config.get("encoding_aes_key", ""),
                    CORP_ID
                )
                self.logger.info("✅ 事件加密工具初始化成功")
            except Exception as e:
                self.logger.warning(f"⚠️ 事件加密工具初始化失败: {str(e)}，将使用明文模式")
                self.event_config["enable_encryption"] = False

    def _get_cached_value(self, cache_key: str) -> Optional[str]:
        """获取缓存值：优先Redis，其次内存"""
        # 1. 尝试从Redis获取
        if redis_client:
            return redis_client.get(cache_key)
        # 2. 尝试从内存获取（校验过期时间）
        if cache_key in self.memory_cache:
            cache_data = self.memory_cache[cache_key]
            if cache_data["expire_time"] > time.time():
                return cache_data["value"]
        # 缓存不存在或已过期
        return None

    def _set_cached_value(self, cache_key: str, value: str, expire_seconds: int):
        """设置缓存值：优先Redis，其次内存"""
        # 1. 存入Redis
        if redis_client:
            redis_client.setex(cache_key, expire_seconds, value)
        # 2. 存入内存（记录过期时间）
        else:
            self.memory_cache[cache_key] = {
                "value": value,
                "expire_time": time.time() + expire_seconds
            }

    def _get_access_token(self) -> str:
        """获取Access Token（带缓存，防止频繁请求企业微信接口）"""
        # 先查缓存
        cached_token = self._get_cached_value(self.TOKEN_CACHE_KEY)
        if cached_token:
            return cached_token

        # 缓存失效，调用企业微信接口获取
        token_url = f"https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={CORP_ID}&corpsecret={APP_SECRET}"
        response = requests.get(token_url).json()
        if response.get("errcode") != 0:
            raise Exception(f"获取Access Token失败：{response['errmsg']}（错误码：{response['errcode']}）")

        # 存入缓存（提前200秒刷新，避免临界点过期）
        self._set_cached_value(self.TOKEN_CACHE_KEY, response["access_token"], TOKEN_EXPIRE)
        return response["access_token"]

    def _get_jsapi_ticket(self) -> str:
        """获取JSAPI Ticket（带缓存，用于生成前端SDK签名）"""
        # 先查缓存
        cached_ticket = self._get_cached_value(self.TICKET_CACHE_KEY)
        if cached_ticket:
            return cached_ticket

        # 缓存失效，用Access Token获取Ticket
        access_token = self._get_access_token()
        ticket_url = f"https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token={access_token}"
        response = requests.get(ticket_url).json()
        if response.get("errcode") != 0:
            raise Exception(f"获取JSAPI Ticket失败：{response['errmsg']}（错误码：{response['errcode']}）")

        # 存入缓存
        self._set_cached_value(self.TICKET_CACHE_KEY, response["ticket"], TICKET_EXPIRE)
        return response["ticket"]

    # -------------------------- 功能1：JS-SDK签名生成 --------------------------
    def generate_js_sdk_sign(self, current_page_url: str) -> Dict:
        """生成前端注入企业微信SDK所需的签名参数"""
        jsapi_ticket = self._get_jsapi_ticket()
        nonce_str = "Wm3WZYTPz0wzccnW"  # 随机字符串（可自定义，建议16位）
        timestamp = int(time.time())
        # 按企业微信规则拼接签名串（顺序不可变！）
        sign_string = f"jsapi_ticket={jsapi_ticket}&noncestr={nonce_str}&timestamp={timestamp}&url={current_page_url}"
        # 生成SHA1签名
        signature = hashlib.sha1(sign_string.encode("utf-8")).hexdigest()
        return {
            "corpId": CORP_ID,
            "nonceStr": nonce_str,
            "timestamp": timestamp,
            "signature": signature
        }

    # -------------------------- 功能2：用户信息相关 --------------------------
    def get_user_by_oauth_code(self, oauth_code: str) -> Dict:
        """通过OAuth2.0授权码（code）获取用户基础信息"""
        access_token = self._get_access_token()
        user_url = f"https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token={access_token}&code={oauth_code}"
        response = requests.get(user_url).json()
        if response.get("errcode") != 0:
            raise Exception(f"通过OAuth Code获取用户失败：{response['errmsg']}（错误码：{response['errcode']}）")
        return {
            "userid": response["UserId"],
            "name": response.get("name", ""),
            "avatar": response.get("Avatar", ""),
            "department": response.get("Department", [])
        }

    def get_user_detail_info(self, userid: str) -> Dict:
        """获取单个用户完整详情（含手机号、职位等，需应用开启对应权限）"""
        access_token = self._get_access_token()
        detail_url = f"https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token={access_token}&userid={userid}"
        response = requests.get(detail_url).json()
        if response.get("errcode") != 0:
            raise Exception(f"获取用户完整详情失败：{response['errmsg']}（错误码：{response['errcode']}）")
        return {
            "userid": response["userid"],
            "name": response["name"],
            "avatar": response.get("avatar", ""),
            "mobile": response.get("mobile", ""),
            "department": response.get("department", []),
            "position": response.get("position", "")
        }

    # -------------------------- 功能3：部门相关 --------------------------
    def get_department_list(self, parent_dept_id: int = 0) -> List[Dict]:
        """获取单层部门列表（仅返回指定父部门的直接子部门，非树形）"""
        access_token = self._get_access_token()
        dept_url = f"https://qyapi.weixin.qq.com/cgi-bin/department/list?access_token={access_token}&id={parent_dept_id}"
        response = requests.get(dept_url).json()
        if response.get("errcode") != 0:
            raise Exception(f"获取部门列表失败：{response['errmsg']}（错误码：{response['errcode']}）")
        # 格式化返回结果（只保留常用字段）
        return [
            {
                "dept_id": dept["id"],
                "dept_name": dept["name"],
                "parent_id": dept["parentid"],
                "order": dept.get("order", 0)  # 部门排序序号
            }
            for dept in response.get("department", [])
        ]

    def _get_department_tree(self, parent_dept_id: int = 0) -> List[Dict]:
        """非递归方式获取全量部门树（避免递归深度问题）"""
        self.logger.info(f"开始获取部门树，父部门ID: {parent_dept_id}")
        dept_tree = []
        # 使用栈存储待处理的部门节点，每个元素是(parent_id, children_list)
        stack = [{"parent_id": parent_dept_id, "children": dept_tree}]
        processed_parent_ids = set()  # 记录已处理的父部门ID，避免重复处理

        while stack:
            current = stack.pop()
            parent_id = current["parent_id"]
            children_list = current["children"]

            # 跳过已处理的父部门ID，防止重复处理和潜在循环
            if parent_id in processed_parent_ids:
                self.logger.debug(f"父部门ID {parent_id} 已处理，跳过")
                continue
            processed_parent_ids.add(parent_id)

            # 获取当前父部门的直接子部门
            try:
                dept_list = self.get_department_list(parent_id)
                self.logger.info(f"获取父部门ID {parent_id} 的子部门，共 {len(dept_list)} 个")
                children_list.extend(dept_list)

                # 将子部门加入栈，继续处理其下的子部门
                for dept in dept_list:
                    dept["children"] = []
                    stack.append({"parent_id": dept["dept_id"], "children": dept["children"]})
            except Exception as e:
                self.logger.error(f"获取部门列表失败（父部门ID: {parent_id}）: {str(e)}")
                continue

        self.logger.info(f"部门树构建完成，根部门数量: {len(dept_tree)}")
        return dept_tree

    # -------------------------- 功能4：用户列表相关 --------------------------
    def get_users_by_department(self, dept_id: int, fetch_child: int = 0) -> List[Dict]:
        """分页获取指定部门的用户列表（支持递归获取子部门用户）"""
        self.logger.info(f"开始获取部门用户，部门ID: {dept_id}, 递归子部门: {fetch_child}")
        all_users = []
        offset = 0
        count = USER_LIST_PAGE_SIZE  # 每次获取的数量

        while True:
            access_token = self._get_access_token()
            user_url = (
                f"https://qyapi.weixin.qq.com/cgi-bin/user/simplelist?access_token={access_token}"
                f"&department_id={dept_id}&fetch_child={fetch_child}&offset={offset}&count={count}"
            )

            try:
                response = requests.get(user_url).json()
                if response.get("errcode") != 0:
                    raise Exception(f"获取部门用户列表失败：{response['errmsg']}（错误码：{response['errcode']}）")

                userlist = response.get("userlist", [])
                self.logger.info(f"获取部门 {dept_id} 用户，偏移量: {offset}, 本次获取: {len(userlist)} 个")

                if not userlist:
                    break  # 没有更多数据

                # 格式化用户数据
                formatted_users = [
                    {
                        "userid": user["userid"],
                        "name": user["name"],
                        "avatar": user.get("avatar", "")
                    } for user in userlist
                ]

                all_users.extend(formatted_users)
                offset += count

                # 如果获取数量小于分页大小，说明已经获取完毕
                if len(userlist) < count:
                    break

            except Exception as e:
                self.logger.error(f"获取部门用户失败（部门ID: {dept_id}, 偏移量: {offset}）: {str(e)}")
                raise

        self.logger.info(f"部门 {dept_id} 用户获取完成，共 {len(all_users)} 个")
        return all_users

    def get_all_enterprise_users(self) -> List[Dict]:
        """获取企业全量用户（遍历所有部门，自动去重，使用多线程优化）"""
        self.logger.info("开始获取企业全量用户")
        all_users = []
        userid_set = set()  # 用集合去重（用户可能属于多个部门）
        thread_pool = None

        try:
            # 获取全量部门树
            dept_tree = self._get_department_tree()
            if not dept_tree:
                self.logger.warning("未获取到任何部门信息")
                return []

            # 如果启用多线程，创建线程池
            if ENABLE_MULTITHREADING and THREAD_COUNT > 1:
                from concurrent.futures import ThreadPoolExecutor
                thread_pool = ThreadPoolExecutor(max_workers=THREAD_COUNT)
                futures = []
                results = []

            # 收集所有部门ID
            dept_ids = []
            stack = dept_tree.copy()
            while stack:
                dept_node = stack.pop()
                dept_id = dept_node["dept_id"]
                dept_ids.append(dept_id)
                if "children" in dept_node and dept_node["children"]:
                    stack.extend(dept_node["children"])

            self.logger.info(f"共发现 {len(dept_ids)} 个部门，开始获取用户数据")

            # 多线程方式获取各部门用户
            if thread_pool:
                def fetch_dept_users(dept_id):
                    try:
                        self.logger.info(f"线程开始处理部门: {dept_id}")
                        return self.get_users_by_department(dept_id, fetch_child=0)
                    except Exception as e:
                        self.logger.error(f"线程处理部门 {dept_id} 失败: {str(e)}")
                        return []

                # 提交所有部门用户获取任务
                for dept_id in dept_ids:
                    futures.append(thread_pool.submit(fetch_dept_users, dept_id))

                # 收集结果
                for future in futures:
                    results.extend(future.result())

                thread_pool.shutdown()
            else:
                # 单线程方式
                for dept_id in dept_ids:
                    try:
                        self.logger.info(f"处理部门: {dept_id}")
                        dept_users = self.get_users_by_department(dept_id, fetch_child=0)
                        results.extend(dept_users)
                    except Exception as e:
                        self.logger.error(f"处理部门 {dept_id} 时出错: {str(e)}")
                        continue

            # 去重处理
            for user in results:
                if user["userid"] not in userid_set:
                    userid_set.add(user["userid"])
                    all_users.append(user)

            self.logger.info(f"企业全量用户获取完成，共 {len(all_users)} 个（去重后）")
            return all_users

        except Exception as e:
            self.logger.error(f"获取企业全量用户失败: {str(e)}", exc_info=True)
            raise
        finally:
            if thread_pool:
                thread_pool.shutdown(wait=False)

    # -------------------------- 功能5：素材上传与审批提交 --------------------------
    def upload_temporary_media(self, media_type: str, file_object) -> str:
        """上传临时素材（图片、文件等），返回media_id用于审批附件"""
        access_token = self._get_access_token()
        upload_url = f"https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token={access_token}&type={media_type}"
        # 构造文件上传参数（需用form-data格式）
        files = {"media": (file_object.filename, file_object.stream, file_object.mimetype)}
        response = requests.post(upload_url, files=files).json()
        if response.get("errcode") != 0:
            raise Exception(f"上传临时素材失败：{response['errmsg']}（错误码：{response['errcode']}）")
        return response["media_id"]

    def is_valid_media_id(self, media_id: str) -> bool:
        """验证media_id是否有效（检查是否为本企业上传且未过期）"""
        # 实际应用中可根据企业微信API查询素材详情来验证
        # 这里简化实现，检查格式并假设近期上传的为有效
        if not media_id or not isinstance(media_id, str):
            return False

        # 企业微信media_id通常包含字母、数字和下划线
        import re
        if not re.match(r'^[A-Za-z0-9_]+$', media_id):
            return False

        # 可以添加缓存检查，验证是否为本系统近期上传的media_id
        return True

    def submit_approval_application(self, approval_params: Dict, media_list: Optional[List[Dict]] = None) -> Dict:
        """提交企业微信审批申请（支持附带素材，允许附件为空）"""
        access_token = self._get_access_token()
        approval_url = f"https://qyapi.weixin.qq.com/cgi-bin/oa/applyevent?access_token={access_token}"

        # 处理素材（如有）
        if media_list and len(media_list) > 0:
            approval_params["attachment"] = []
            for media in media_list:
                # 只处理有效的媒体文件
                if media.get("file_obj"):
                    media_id = self.upload_temporary_media(media["type"], media["file_obj"])
                    approval_params["attachment"].append({
                        "media_id": media_id,
                        "type": media["type"]
                    })

        # 提交审批申请
        response = requests.post(approval_url, json=approval_params).json()
        if response.get("errcode") != 0:
            raise Exception(f"提交审批申请失败：{response['errmsg']}（错误码：{response['errcode']}）")

        return {
            "sp_no": response["sp_no"],
            "errcode": response["errcode"],
            "errmsg": response["errmsg"]
        }

    # -------------------------- 功能6：发送应用消息 --------------------------
    def send_application_message(self, msg_params: Dict) -> Dict:
        """发送企业微信应用消息"""
        access_token = self._get_access_token()
        msg_url = f"https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token={access_token}"

        # 确保消息参数包含必要字段
        required_fields = ["touser", "msgtype"]
        for field in required_fields:
            if field not in msg_params:
                raise ValueError(f"消息参数缺少必要字段: {field}")

        # 添加应用ID
        msg_params["agentid"] = AGENT_ID

        response = requests.post(msg_url, json=msg_params).json()
        if response.get("errcode") != 0:
            raise Exception(f"发送应用消息失败：{response['errmsg']}（错误码：{response['errcode']}）")

        return response

    # -------------------------- 新增：功能7：事件订阅相关方法 --------------------------
    def set_event_callback(self, url, token, encoding_aes_key):
        """设置企业微信事件回调配置"""
        access_token = self._get_access_token()
        callback_url = f"https://qyapi.weixin.qq.com/cgi-bin/callback/set?access_token={access_token}"

        data = {
            "url": url,
            "token": token,
            "encodingaeskey": encoding_aes_key,
            "corpid": CORP_ID
        }

        response = requests.post(callback_url, json=data).json()
        if response.get("errcode") != 0:
            raise Exception(f"设置事件回调失败：{response['errmsg']}（错误码：{response['errcode']}）")
        return response

    def get_event_callback(self):
        """获取当前事件回调配置"""
        access_token = self._get_access_token()
        callback_url = f"https://qyapi.weixin.qq.com/cgi-bin/callback/get?access_token={access_token}"

        response = requests.get(callback_url).json()
        if response.get("errcode") != 0:
            raise Exception(f"获取事件回调配置失败：{response['errmsg']}（错误码：{response['errcode']}）")
        return response

    def handle_event(self, event_data):
        """处理接收到的企业微信事件"""
        event_type = event_data.get("Event")
        self.logger.info(f"收到企业微信事件：{event_type}，数据：{json.dumps(event_data, ensure_ascii=False)}")

        # 根据事件类型进行不同处理
        handlers = {
            # 通讯录事件
            "create_user": self._handle_user_create,
            "update_user": self._handle_user_update,
            "delete_user": self._handle_user_delete,
            "create_party": self._handle_party_create,
            "update_party": self._handle_party_update,
            "delete_party": self._handle_party_delete,

            # 消息事件
            "message": self._handle_message,

            # 其他事件可以在这里添加
        }

        # 调用对应的处理器
        handler = handlers.get(event_type, self._handle_default_event)
        return handler(event_data)

    # 各类事件的具体处理方法
    def _handle_user_create(self, event_data):
        """处理用户创建事件"""
        user_id = event_data.get("UserID")
        self.logger.info(f"用户创建事件：{user_id}")
        # 可以在这里添加同步用户信息等逻辑
        return {"success": True, "message": f"处理用户创建事件：{user_id}"}

    def _handle_user_update(self, event_data):
        """处理用户更新事件"""
        user_id = event_data.get("UserID")
        self.logger.info(f"用户更新事件：{user_id}")
        # 可以在这里添加同步用户信息等逻辑
        return {"success": True, "message": f"处理用户更新事件：{user_id}"}

    def _handle_user_delete(self, event_data):
        """处理用户删除事件"""
        user_id = event_data.get("UserID")
        self.logger.info(f"用户删除事件：{user_id}")
        # 可以在这里添加清理用户信息等逻辑
        return {"success": True, "message": f"处理用户删除事件：{user_id}"}

    def _handle_party_create(self, event_data):
        """处理部门创建事件"""
        party_id = event_data.get("Id")
        self.logger.info(f"部门创建事件：{party_id}")
        # 可以在这里添加同步部门信息等逻辑
        return {"success": True, "message": f"处理部门创建事件：{party_id}"}

    def _handle_party_update(self, event_data):
        """处理部门更新事件"""
        party_id = event_data.get("Id")
        self.logger.info(f"部门更新事件：{party_id}")
        # 可以在这里添加同步部门信息等逻辑
        return {"success": True, "message": f"处理部门更新事件：{party_id}"}

    def _handle_party_delete(self, event_data):
        """处理部门删除事件"""
        party_id = event_data.get("Id")
        self.logger.info(f"部门删除事件：{party_id}")
        # 可以在这里添加清理部门信息等逻辑
        return {"success": True, "message": f"处理部门删除事件：{party_id}"}

    def _handle_message(self, event_data):
        """处理消息事件"""
        sender = event_data.get("FromUserName")
        self.logger.info(f"收到来自 {sender} 的消息")
        # 可以在这里添加消息处理逻辑
        return {"success": True, "message": f"处理来自 {sender} 的消息"}

    def _handle_default_event(self, event_data):
        """默认事件处理器"""
        event_type = event_data.get("Event")
        self.logger.info(f"收到未处理的事件类型：{event_type}")
        return {"success": True, "message": f"已接收未处理事件：{event_type}"}


# -------------------------- 6. 日志配置与端口管理 --------------------------
def setup_logging(port):
    """配置日志系统，按天生成日志文件，包含端口信息"""
    # 创建日志目录（如果不存在）
    log_dir = "logs"
    if not os.path.exists(log_dir):
        os.makedirs(log_dir)

    # 日志格式，包含端口信息
    log_format = f'%(asctime)s - %(levelname)s - 端口:{port} - %(message)s'
    formatter = logging.Formatter(log_format)

    # 控制台处理器
    console_handler = logging.StreamHandler()
    console_handler.setFormatter(formatter)

    # 文件处理器，按天轮转
    file_handler = TimedRotatingFileHandler(
        os.path.join(log_dir, 'wecom_api.log'),
        when='midnight',
        interval=1,
        backupCount=30,  # 保留30天日志
        encoding='utf-8'
    )
    file_handler.setFormatter(formatter)
    file_handler.suffix = "%Y%m%d"  # 日志文件后缀格式

    # 配置根日志
    logging.basicConfig(
        level=logging.INFO,
        handlers=[console_handler, file_handler]
    )

    return logging.getLogger(__name__)


def find_available_port(start_port=5000, max_attempts=10):
    """寻找可用的端口，从start_port开始尝试"""
    for i in range(max_attempts):
        port = start_port + i
        try:
            # 尝试绑定端口，如果成功则返回该端口
            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
                s.bind(('0.0.0.0', port))
            return port
        except OSError:
            continue

    # 如果多次尝试都失败，抛出异常
    raise RuntimeError(f"在端口范围 {start_port}-{start_port + max_attempts - 1} 内未找到可用端口")


def generate_api_endpoints_file():
    """生成API接口清单到当前目录下的txt文件"""
    # 定义接口清单内容，包含事件相关接口
    endpoints = [
        {
            "路径": "/api/js-sdk-sign",
            "方法": "GET",
            "描述": "生成前端注入企业微信SDK所需的签名参数",
            "参数": "url (必填): 当前页面的URL"
        },
        {
            "路径": "/api/user/info",
            "方法": "GET",
            "描述": "通过OAuth授权code获取用户完整信息",
            "参数": "code (必填): 企业微信OAuth2.0授权后返回的临时code"
        },
        {
            "路径": "/api/departments",
            "方法": "GET",
            "描述": "获取指定父部门的直接子部门列表",
            "参数": "parent_id (可选，默认0): 父部门ID"
        },
        {
            "路径": "/api/department/users",
            "方法": "GET",
            "描述": "获取指定部门下的用户列表",
            "参数": "dept_id (必填): 部门ID; fetch_child (可选，默认0): 是否递归获取子部门用户"
        },
        {
            "路径": "/api/all-users",
            "方法": "GET",
            "描述": "获取企业所有用户",
            "参数": "无"
        },
        {
            "路径": "/api/send-message",
            "方法": "POST",
            "描述": "向指定用户/部门/标签发送企业微信应用消息",
            "参数": "JSON格式的消息参数"
        },
        {
            "路径": "/api/submit-approval",
            "方法": "POST",
            "描述": "提交企业微信审批申请",
            "参数": "JSON格式的审批参数，嵌套在approvalData字段中"
        },
        {
            "路径": "/api/upload-media",
            "方法": "POST",
            "描述": "上传临时素材到企业微信服务器",
            "参数": "type (必填): 媒体类型; file (必填): 要上传的文件"
        },
        # 新增：事件相关接口
        {
            "路径": "/api/event/set-callback",
            "方法": "POST",
            "描述": "设置企业微信事件回调配置",
            "参数": "url (必填): 回调URL; token (必填): 验证token; encoding_aes_key (必填): 加密密钥"
        },
        {
            "路径": "/api/event/get-callback",
            "方法": "GET",
            "描述": "获取当前事件回调配置",
            "参数": "无"
        },
        {
            "路径": EVENT_CONFIG["callback_url"],
            "方法": "GET/POST",
            "描述": "企业微信事件回调接收地址",
            "参数": "自动由企业微信服务器调用"
        }
    ]

    # 生成文件内容
    content = "📋 API接口清单:\n"
    content += "-" * 60 + "\n"

    for endpoint in endpoints:
        content += f"路径: {endpoint['路径']}\n"
        content += f"方法: {endpoint['方法']}\n"
        content += f"描述: {endpoint['描述']}\n"
        content += f"参数: {endpoint['参数']}\n"
        content += "-" * 60 + "\n"

    # 写入文件
    try:
        with open("api_endpoints.txt", "w", encoding="utf-8") as f:
            f.write(content)
        return True
    except Exception as e:
        logging.error(f"生成API接口清单文件失败: {str(e)}")
        return False


# -------------------------- 7. Flask应用初始化与路由定义 --------------------------
app = Flask(__name__)
# 初始化CORS，允许跨域请求
CORS(
    app,
    resources={r"/api/*": {
        "origins": CORS_CONFIG["allowed_origins"],
        "supports_credentials": CORS_CONFIG["supports_credentials"]
    }}
)

wecom_client = WeComClient()
logger = None  # 日志实例将在服务启动时初始化


# 错误处理装饰器
def api_response_decorator(func):
    def wrapper(*args, **kwargs):
        try:
            result = func(*args, **kwargs)
            return jsonify({
                "success": True,
                "data": result
            })
        except Exception as e:
            # 记录错误日志
            if logger:
                logger.error(f"接口调用失败: {str(e)}", exc_info=True)
            return jsonify({
                "success": False,
                "error": str(e)
            }), 500

    wrapper.__name__ = func.__name__
    return wrapper


# JS-SDK签名接口
@app.route("/api/js-sdk-sign", methods=["GET"])
@api_response_decorator
def get_js_sdk_sign():
    url = request.args.get("url")
    if not url:
        raise ValueError("缺少参数：url")
    return wecom_client.generate_js_sdk_sign(url)


# 用户信息接口
@app.route("/api/user/info", methods=["GET"])
@api_response_decorator
def get_user_info():
    code = request.args.get("code")
    if not code:
        raise ValueError("缺少参数：code")
    user_info = wecom_client.get_user_by_oauth_code(code)
    # 获取详细信息
    return wecom_client.get_user_detail_info(user_info["userid"])


# 部门列表接口
@app.route("/api/departments", methods=["GET"])
@api_response_decorator
def get_departments():
    parent_id = request.args.get("parent_id", 0, type=int)
    return wecom_client.get_department_list(parent_id)


@app.route("/api/department/users", methods=["GET"])
@api_response_decorator
def get_department_users():
    # 获取查询参数并进行彻底解码
    query_string = request.query_string.decode('utf-8')
    import urllib.parse

    # 进行多次URL解码，确保完全解码
    decoded = False
    max_attempts = 3
    attempt = 0
    while attempt < max_attempts and not decoded:
        try:
            # 检查是否还有需要解码的内容
            if query_string != urllib.parse.unquote(query_string):
                query_string = urllib.parse.unquote(query_string)
                attempt += 1
            else:
                decoded = True
        except Exception as e:
            logger.warning(f"URL解码尝试失败（第{attempt + 1}次）: {str(e)}")
            break

    dept_id = None
    fetch_child = 0  # 默认值

    # 记录解码后的查询字符串，便于调试
    logger.info(f"完全解码后的查询字符串: {query_string}")

    # 提前导入必要模块
    import re
    import json

    try:
        # 1. 尝试解析JSON格式（标准方式）
        # 处理可能的单引号
        normalized_query = query_string.replace("'", "\"")
        params = json.loads(normalized_query)
        logger.info(f"解析JSON查询参数成功: {params}")

        dept_id = params.get('dept_id')
        fetch_child = int(params.get('fetch_child', 0)) if params.get('fetch_child') is not None else 0
        logger.info(f"从JSON中提取参数 - dept_id: {dept_id}, fetch_child: {fetch_child}")
    except json.JSONDecodeError as e:
        logger.warning(f"JSON解析失败: {str(e)}，将尝试其他解析方式")

        # 2. 预处理查询字符串：移除所有引号和特殊字符，简化匹配
        cleaned_query = query_string.replace('"', '').replace("'", "").replace('{', '').replace('}', '')
        logger.info(f"预处理后的查询字符串: {cleaned_query}")

        # 3. 优先匹配dept_id后的数字（最可靠的方式）
        dept_pattern = re.compile(r'dept_id\s*[:=]\s*(\d+)', re.IGNORECASE)
        dept_match = dept_pattern.search(cleaned_query)
        if dept_match:
            dept_id = dept_match.group(1)
            logger.info(f"通过dept_id模式匹配提取: {dept_id}")

        # 4. 处理fetch_child参数
        fetch_pattern = re.compile(r'fetch_child\s*[:=]\s*(\d+)', re.IGNORECASE)
        fetch_match = fetch_pattern.search(cleaned_query)
        if fetch_match:
            fetch_child = int(fetch_match.group(1))
            logger.info(f"通过fetch_child模式匹配提取: {fetch_child}")
        elif re.search(r'fetch_child\s*[:=]\s*null', cleaned_query, re.IGNORECASE):
            fetch_child = 0
            logger.info(f"检测到fetch_child为null，设置为默认值: {fetch_child}")

        # 5. 备选方案：尝试标准查询参数
        if dept_id is None:
            dept_id = request.args.get('dept_id')
            if dept_id:
                logger.info(f"从标准查询参数提取dept_id: {dept_id}")

        # 6. 最后的手段：在万不得已的情况下才提取第一个数字
        if dept_id is None:
            number_match = re.search(r'\d+', cleaned_query)
            if number_match:
                dept_id = number_match.group(0)
                logger.warning(f"只能从查询字符串中提取到数字作为dept_id: {dept_id}，可能不准确")

        logger.info(f"最终提取的参数 - dept_id: {dept_id}, fetch_child: {fetch_child}")
    except Exception as e:
        logger.error(f"参数处理异常: {str(e)}")

    # 检查dept_id是否存在且有效
    if not dept_id:
        logger.error(f"dept_id参数缺失，当前值: {dept_id}")
        raise ValueError("缺少参数：dept_id")

    # 确保dept_id是整数
    try:
        dept_id = int(dept_id)
    except (ValueError, TypeError) as e:
        logger.error(f"dept_id类型转换失败，值: {dept_id}，错误: {str(e)}")
        raise ValueError("参数dept_id必须是整数")

    # 获取部门用户数据
    users = wecom_client.get_users_by_department(dept_id, fetch_child)

    # 确保返回数据可正确序列化
    try:
        json.dumps(users)
        logger.info(f"部门 {dept_id} 的用户数据序列化正常，共 {len(users)} 条记录")
    except Exception as e:
        logger.error(f"用户数据序列化失败: {str(e)}")
        from datetime import datetime
        def serialize(obj):
            if isinstance(obj, datetime):
                return obj.isoformat()
            return str(obj)

        users = json.loads(json.dumps(users, default=serialize))
        logger.info(f"部门 {dept_id} 的用户数据已转换为可序列化格式")

    return users


# 全量用户接口
@app.route("/api/all-users", methods=["GET"])
@api_response_decorator
def get_all_users():
    global logger
    if logger:
        logger.info("接收到全量用户获取请求")
    return wecom_client.get_all_enterprise_users()


# 发送消息接口
@app.route("/api/send-message", methods=["POST"])
@api_response_decorator
def send_message():
    data = request.json
    return wecom_client.send_application_message(data)


# 提交审批接口（带附件校验）
@app.route("/api/submit-approval", methods=["POST"])
@api_response_decorator
def submit_approval():
    # 获取请求体数据
    data = request.json

    # 从approvalData字段提取参数，增加格式校验
    approval_data = data.get("approvalData", {})
    if not approval_data:
        logger.error("请求体中缺少approvalData字段或其值为空")
        raise ValueError("请求格式错误：缺少approvalData字段或内容为空")

    # 验证必填字段
    required_fields = ["creator_userid", "template_id"]
    for field in required_fields:
        if field not in approval_data:
            logger.error(f"approvalData中缺少必填字段: {field}")
            raise ValueError(f"缺少必填参数：{field}（需包含在approvalData中）")

    # 校验附件字段：确保仅包含media_id，且有效
    try:
        # 递归查找所有附件字段（适配不同表单结构）
        def validate_attachments(contents):
            """验证附件，允许为空，只验证存在的附件"""
            if not isinstance(contents, list) or len(contents) == 0:
                return  # 允许空附件

            for item in contents:
                if item.get("control") in ["File", "Image"]:  # 匹配附件/图片类型控件
                    files = item.get("value", {}).get("file", [])
                    # 允许没有文件，但如果有文件则必须包含media_id
                    if files and len(files) > 0:
                        for file_info in files:
                            # 检查是否包含media_id，而非文件内容
                            if "media_id" not in file_info:
                                raise ValueError(f"附件格式错误：缺少media_id（{file_info}）")
                            media_id = file_info["media_id"]
                            # 验证media_id有效性
                            if not wecom_client.is_valid_media_id(media_id):
                                raise ValueError(f"无效的附件ID：{media_id}（请重新上传文件）")
                # 处理嵌套结构（如表格中的附件）
                if "children" in item.get("value", {}).get("table", {}):
                    validate_attachments(item["value"]["table"]["children"])

        # 执行附件校验
        apply_contents = approval_data.get("apply_data", {}).get("contents", [])
        validate_attachments(apply_contents)
        logger.info("附件校验通过，所有media_id均有效")

    except ValueError as e:
        logger.error(f"附件校验失败：{str(e)}")
        raise e
    except Exception as e:
        logger.error(f"附件处理异常：{str(e)}")
        raise ValueError("附件格式错误，请检查后重试")

    # 调用企业微信API提交审批（使用从approvalData提取的参数）
    logger.info(f"准备提交审批，参数: {approval_data}")
    return wecom_client.submit_approval_application(approval_data)


# 上传临时素材接口
@app.route("/api/upload-media", methods=["POST"])
@api_response_decorator
def upload_media():
    # 获取媒体类型参数
    media_type = request.form.get("type")
    if not media_type:
        raise ValueError("缺少参数：type（媒体类型）")

    # 验证媒体类型是否合法
    valid_types = ["image", "voice", "video", "file"]
    if media_type not in valid_types:
        raise ValueError(f"不支持的媒体类型：{media_type}，支持的类型为：{', '.join(valid_types)}")

    # 获取上传的文件
    if "file" not in request.files:
        raise ValueError("缺少上传文件：请使用file字段上传文件")

    file = request.files["file"]
    if file.filename == "":
        raise ValueError("未选择要上传的文件")

    # 调用上传方法
    media_id = wecom_client.upload_temporary_media(media_type, file)
    return {
        "media_id": media_id,
        "type": media_type,
        "filename": file.filename,
        "message": "文件上传成功"
    }


# -------------------------- 新增：8. 事件回调路由 --------------------------
@app.route(EVENT_CONFIG["callback_url"], methods=["GET", "POST"])
def event_callback():
    """企业微信事件回调处理接口"""
    # GET请求用于验证回调URL
    if request.method == "GET":
        return handle_callback_verification()

    # POST请求用于接收事件推送
    return handle_event_push()


def handle_callback_verification():
    """处理企业微信回调URL验证"""
    event_config = EVENT_CONFIG
    token = event_config.get("token", "")

    # 获取请求参数
    signature = request.args.get("msg_signature")
    timestamp = request.args.get("timestamp")
    nonce = request.args.get("nonce")
    echostr = request.args.get("echostr")

    # 验证签名
    try:
        # 构造签名字符串
        sign_str = ''.join(sorted([token, timestamp, nonce, echostr]))
        # 计算签名
        calc_signature = hashlib.sha1(sign_str.encode('utf-8')).hexdigest()

        # 验证签名
        if calc_signature == signature:
            logger.info("回调URL验证成功")
            # 如果启用了加密，需要解密echostr
            if event_config.get("enable_encryption", True) and wecom_client.crypto:
                return wecom_client.crypto.decrypt(echostr)
            return echostr
        else:
            logger.error("回调URL验证失败：签名不匹配")
            return "签名验证失败", 403
    except Exception as e:
        logger.error(f"回调URL验证出错：{str(e)}")
        return f"验证出错：{str(e)}", 500


def handle_event_push():
    """处理企业微信事件推送"""
    event_config = EVENT_CONFIG
    token = event_config.get("token", "")

    # 获取请求参数
    signature = request.args.get("msg_signature")
    timestamp = request.args.get("timestamp")
    nonce = request.args.get("nonce")

    # 读取请求体
    data = request.data.decode('utf-8')

    try:
        # 验证签名
        sign_str = ''.join(sorted([token, timestamp, nonce, data]))
        calc_signature = hashlib.sha1(sign_str.encode('utf-8')).hexdigest()

        if calc_signature != signature:
            logger.error("事件推送验证失败：签名不匹配")
            return "签名验证失败", 403

        # 解密数据（如果启用了加密）
        if event_config.get("enable_encryption", True) and wecom_client.crypto:
            # 解析XML获取Encrypt字段
            import xml.etree.ElementTree as ET
            root = ET.fromstring(data)
            encrypt_data = root.find("Encrypt").text
            data = wecom_client.crypto.decrypt(encrypt_data)

        # 解析事件数据（XML格式）
        import xml.etree.ElementTree as ET
        root = ET.fromstring(data)
        event_data = {}
        for child in root:
            event_data[child.tag] = child.text

        # 处理事件
        result = wecom_client.handle_event(event_data)

        # 构造响应
        response_xml = "<xml><ReturnCode>0</ReturnCode><ReturnMsg></ReturnMsg></xml>"

        # 如果启用了加密，加密响应
        if event_config.get("enable_encryption", True) and wecom_client.crypto:
            encrypted_response = wecom_client.crypto.encrypt(response_xml)
            response_xml = f"<xml><Encrypt>{encrypted_response}</Encrypt></xml>"

        return response_xml, 200, {'Content-Type': 'application/xml'}

    except Exception as e:
        logger.error(f"处理事件推送出错：{str(e)}", exc_info=True)
        error_xml = "<xml><ReturnCode>1</ReturnCode><ReturnMsg>处理失败</ReturnMsg></xml>"
        return error_xml, 500, {'Content-Type': 'application/xml'}


# -------------------------- 新增：9. 事件回调配置管理接口 --------------------------
@app.route("/api/event/set-callback", methods=["POST"])
@api_response_decorator
def set_event_callback():
    """设置事件回调配置"""
    data = request.json
    return wecom_client.set_event_callback(
        data.get("url"),
        data.get("token"),
        data.get("encoding_aes_key")
    )


@app.route("/api/event/get-callback", methods=["GET"])
@api_response_decorator
def get_event_callback():
    """获取当前事件回调配置"""
    return wecom_client.get_event_callback()


# -------------------------- 10. 服务相关类和函数 --------------------------
class WeComApiService:
    """企业微信API服务类，用于作为系统服务运行"""

    def __init__(self):
        self.running = False
        self.restart_count = 0
        self.logger = None
        self.port = None

    def start_service(self):
        """启动服务"""
        global logger
        try:
            # 查找可用端口
            self.port = find_available_port(5000)

            # 初始化日志
            self.logger = setup_logging(self.port)
            logger = self.logger  # 初始化全局日志实例

            # 打印启动信息
            self.logger.info("✅ 企业微信API服务启动中...")
            self.logger.info(f"ℹ️  企业ID: {CORP_ID}")
            self.logger.info(f"ℹ️  应用ID: {AGENT_ID}")
            self.logger.info(f"ℹ️  跨域配置: 允许来源 {CORS_CONFIG['allowed_origins']}")
            self.logger.info(
                f"ℹ️  线程配置: 多线程{'启用' if ENABLE_MULTITHREADING else '禁用'}, 线程数: {THREAD_COUNT}")
            # 新增：事件配置信息
            self.logger.info(f"ℹ️  事件回调URL: {EVENT_CONFIG['callback_url']}")
            self.logger.info(f"ℹ️  事件加密: {'启用' if EVENT_CONFIG.get('enable_encryption', True) else '禁用'}")

            # 生成API接口清单文件
            if generate_api_endpoints_file():
                self.logger.info("✅ API接口清单已生成: api_endpoints.txt")
            else:
                self.logger.warning("⚠️ API接口清单生成失败")

            self.running = True
            self.logger.info(f"🚀 服务在端口 {self.port} 启动成功")

            # 启动Flask应用
            app.run(
                host="0.0.0.0",
                port=self.port,
                debug=False,
                use_reloader=False,
                threaded=ENABLE_MULTITHREADING
            )

        except Exception as e:
            self.logger.error(f"服务启动失败: {str(e)}")
            self.running = False
            raise

    def stop_service(self):
        """停止服务"""
        self.running = False
        if self.logger:
            self.logger.info("⏹️ 企业微信API服务已停止")

    def run(self):
        """运行服务，包含重启机制"""
        while self.restart_count < SERVICE_CONFIG["max_restarts"]:
            try:
                self.start_service()
                # 如果服务正常启动并运行，重置重启计数
                if self.running:
                    self.restart_count = 0
                    break
            except Exception as e:
                self.restart_count += 1
                self.logger.error(
                    f"服务运行出错，将在 {SERVICE_CONFIG['restart_delay']} 秒后尝试重启（第 {self.restart_count}/{SERVICE_CONFIG['max_restarts']} 次）")
                time.sleep(SERVICE_CONFIG["restart_delay"])

        if self.restart_count >= SERVICE_CONFIG["max_restarts"]:
            self.logger.error(f"服务已达到最大重启次数 ({SERVICE_CONFIG['max_restarts']} 次)，将停止运行")


if IS_WINDOWS:
    class WindowsService(win32serviceutil.ServiceFramework):
        """Windows服务类"""
        _svc_name_ = SERVICE_CONFIG["service_name"]
        _svc_display_name_ = SERVICE_CONFIG["display_name"]
        _svc_description_ = SERVICE_CONFIG["description"]

        def __init__(self, args):
            win32serviceutil.ServiceFramework.__init__(self, args)
            self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
            self.service = WeComApiService()
            pythoncom.CoInitialize()

        def SvcStop(self):
            """停止服务"""
            self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
            self.service.stop_service()
            win32event.SetEvent(self.hWaitStop)
            self.ReportServiceStatus(win32service.SERVICE_STOPPED)

        def SvcDoRun(self):
            """运行服务"""
            servicemanager.LogMsg(
                servicemanager.EVENTLOG_INFORMATION_TYPE,
                servicemanager.PYS_SERVICE_STARTED,
                (self._svc_name_, "")
            )
            # 终止可能存在的同名服务
            kill_existing_service(SERVICE_CONFIG["service_name"])
            self.service.run()
            win32event.WaitForSingleObject(self.hWaitStop, win32event.INFINITE)
            pythoncom.CoUninitialize()


# -------------------------- 11. 应用入口 --------------------------
def main():
    if IS_WINDOWS:
        # Windows系统，处理服务相关命令
        if len(sys.argv) == 1:
            servicemanager.Initialize()
            servicemanager.PrepareToHostSingle(WindowsService)
            servicemanager.StartServiceCtrlDispatcher()
        else:
            # 支持的命令：install, remove, start, stop, restart
            win32serviceutil.HandleCommandLine(WindowsService)
    else:
        # 非Windows系统，直接运行
        try:
            # 终止可能存在的同名服务
            kill_existing_service(SERVICE_CONFIG["service_name"])

            service = WeComApiService()
            service.run()
        except Exception as e:
            print(f"❌ 服务启动失败: {str(e)}")
            exit(1)


if __name__ == "__main__":
    main()
